//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#ifndef _win32
#include <core.h>
#endif
#include <stdlib.h>
#include "arch/cc.h"
#include "arch/sys_arch.h"
#include "lwip/opt.h"

extern "C" void mem_init(void);
extern "C" void netif_init(void);
extern "C" void memp_init(void);
extern "C" void pbuf_init(void);
extern "C" void stats_init(void);
extern "C" void raw_init(void);
extern "C" void pppInit(void *arg);
extern "C" void tcpip_init(void (* tcpip_init_done)(void *), void *arg);
extern Thread *CreateSystemThread(
        threadroutine_t threadroutine, void *pvParameter,
        ScheduleClass *pScheduleClass, uint_t uSchedulePolicy);

extern "C" int netif_del(wchar_t *ethernetName);

extern "C" void set_dhcp_thread_exit_arg(void *pNetif, void *pExitArg);

////////////////////rtl8139///////////////////////////////
#define DRVINITIALIZE   0x00000001
#define ENABLEIRQ       0x00000002
#define DISABLEIRQ      0x00000003
#define DRVSHUTDOWN     0x00000004

extern "C" sys_mbox_t SYS_MBOX_NULL;
extern "C" sys_sem_t  SYS_SEM_NULL;

sys_mbox_t SYS_MBOX_NULL = 0;
sys_sem_t  SYS_SEM_NULL = 0;

typedef struct ThreadArg
{
    IDeviceDriver *pDriver;
    void *pNetif;
    int *pExitFlag;
}ThreadArg, *PThreadArg;

typedef struct NetThreadExitArg
{
    int isExit;
    Thread *pThread;
}NetThreadExitArg, *PNetThreadExitArg, DHCPThreadExitArg, *PDHCPThreadExitArg;

extern "C" unsigned int inet_addr(const char *cp);

extern char g_localname[25];

extern "C" void loop_init();

EXTERN_C int DHCP_Client(void *parg);
extern int eth_deinit(wchar_t *ethernetName);


extern "C" u32_t sys_jiffies(void) /* since power up. */
{
	return DzGetElapsedTicks();
}

extern "C" void LwipSleep(int ms)
{
    DzSleep(ms/10, NULL);
}

extern "C" int Lwip_Rand(void)
{
    SystemTime tm;
    DzGetSystemTime(&tm);
    return tm.microseconds % 4096;
}

extern "C" void *
        extern_etherifnet_init(void* haddr, u32_t ip, u32_t mask, u32_t gateway, u32_t priDns, void *pNetDriver, wchar_t * deviceName, void *pNetThreadExitArg);

extern "C" void
        extern_ethernetif_input(unsigned char *databuf, int datalen, void *pNetif);

extern "C" void
        extern_etherifnet_changeIp(wchar_t *ethernetName, void* haddr, u32_t ip, u32_t mask, u32_t gateway, u32_t priDns, u32_t secdDns);

extern "C" s32_t
        Lwip_SetNetifAttribute(void *pnetif, u32_t ip, u32_t mask, u32_t gw, u32_t firstdns, u32_t seconddns);


Int32 NetReadThreadEx(void *parg)
{
    int quitFlag = 0;
    ThreadArg *pArg = (ThreadArg *)parg;
    assert(NULL != pArg);

    IDeviceDriver *pDriver = pArg->pDriver;

    ECode hr;
    MemoryBuf_<1600> ebbOutData;

    if (NULL == pArg->pExitFlag)
        pArg->pExitFlag = &quitFlag;

    while (0 == *(pArg->pExitFlag)) {
        hr = (pDriver->Read(0, 0, &ebbOutData, NULL));
        if (FAILED(hr)) {
            kprintf("*ERROR* failed to read from driver\n");
            (pArg->pDriver)->Release();
            return hr;
        } 
        extern_ethernetif_input((unsigned char*)ebbOutData.GetPayload(), ebbOutData.GetUsed(), pArg->pNetif);
    }

    //kprintf("\n\n\n\quit quit quit quit quit\n\n\n\n");
    (pArg->pDriver)->Release();
    free(pArg);
    return 0;

}

ELAPI _CProfile_GetAString(AString appName, AString keyName,
        AStringBuf *pReturnedString);

extern int RegisterInterface();

void Initnetdata(int* a_ip, int * a_mask, int * a_gw, int * a_dns, int deviceNo)
{
    AStringBuf_<15> netid;
    AStringBuf_<15> ip;
    AStringBuf_<15> mask;
    AStringBuf_<15> router;
    AStringBuf_<15> dns;
    ECode ec;
    AString identifier;
    static bool hasInitLocalName = false;

    if (0 == deviceNo)
        identifier = "Tcpip";
    else 
        identifier = "Tcpip1";

    ec = _CProfile_GetAString(identifier, "NetID", &netid);
    if(ec != NOERROR){
        goto exit;
    }
    ec = _CProfile_GetAString(identifier, "LocalIP", &ip);
    if(ec != NOERROR){
        goto exit;
    }
    ec = _CProfile_GetAString(identifier, "SubnetMask", &mask);
    if(ec != NOERROR){
        goto exit;
    }
    ec = _CProfile_GetAString(identifier, "DefaultGateway", &router);
    if(ec != NOERROR){
        goto exit;
    }
    ec = _CProfile_GetAString(identifier, "Nameserver", &dns);
    if (!hasInitLocalName) {
        strncpy(&g_localname[0], netid, strlen(netid));
        hasInitLocalName = true;
    }

    *a_mask = inet_addr(mask);
    if (-1 == *a_mask)
        *a_mask = inet_addr("255.255.255.0");

    *a_ip = inet_addr(ip);
    if (-1 == *a_ip)
        *a_ip = 0;
    
    *a_gw = inet_addr(router);
    if (-1 == *a_gw)
        *a_gw = 0;

    *a_dns = inet_addr(dns);
    if (-1 == *a_dns)
        *a_dns = 0;

    return;
exit:
    //assert(0);
    kprintf("Read net work config failed!!\n");
}

static int get_deviceNo(wchar_t *ethernetName)
{
    int deviceNo;
    wchar_t *p, q;

    if (NULL == ethernetName)
        return -1;

   
p = ethernetName;
    while (*(++p) != 0);
    while ( (q = *(--p)) >= L'0' && q <= L'9');
    p++;

    deviceNo = _wtoi(p);
    return deviceNo;
}

extern int SetNetAttribute(void *pnetif, u32_t ip, u32_t mask, u32_t gw, u32_t firstdns, u32_t seconddns)
{
    return Lwip_SetNetifAttribute(pnetif, ip, mask, gw, firstdns, seconddns);
}

extern void eth_init(wchar_t *ethernetName)
{
    MemoryBuf_<6> ebbOutData;
    ECode hr;
    Int32 ip, mask, gw, dns;
    IDeviceDriver *pNetDriver;
    ThreadArg *pArg;
    NetThreadExitArg *pThreadExitArg;
    wchar_t serviceName[32];
    int deviceNo;

    pArg = (PThreadArg )malloc(sizeof(ThreadArg));
    if (NULL == pArg) {
        kprintf("Not Enough Memory!\n");
        return;
    }
    pThreadExitArg = (PNetThreadExitArg)malloc(sizeof(NetThreadExitArg));
    if (NULL == pThreadExitArg) {
        kprintf("Not Enough Memory!\n");
        free(pArg);
        return;
    }
    pThreadExitArg->isExit = 0;
    pArg->pExitFlag = &(pThreadExitArg->isExit);

    deviceNo = get_deviceNo(ethernetName);
    memset(serviceName, 0, sizeof(serviceName));
    wcscpy(serviceName, L"device:");
    assert(wcslen(ethernetName) <= (sizeof(serviceName) / sizeof(wchar_t)) - wcslen(L"device:") - 1);
    wcscat(serviceName, ethernetName);

    hr = DzFindService(serviceName, (IInterface **)&pNetDriver);//serviceName such as L"device:eth0"
    if (FAILED(hr)) {
        kprintf("Init Ethernet %d failed!!!!\n", deviceNo);
        free(pArg);
        return;
    }
    pArg->pDriver = pNetDriver;
    hr = pNetDriver->Control(DRVINITIALIZE, NULL_MEMORYBUF, &ebbOutData, NULL);
    if (FAILED(hr)) {
        kprintf("*ERROR* failed to connect driver pDriver=%08x\n", pNetDriver);
        free(pArg);
        return;
    }

    Initnetdata(&ip, &mask, &gw, &dns, deviceNo);
    pArg->pNetif = extern_etherifnet_init((unsigned char*)ebbOutData.GetPayload(), ip, mask, gw, dns, 
            (void *)pNetDriver, ethernetName, (void *)pThreadExitArg);
    if (NULL == pArg->pNetif) {
        kprintf("*ERROR* Init net interface failed!\n");
        free(pArg);
        return;
    }

    Thread *pThread = CreateSystemThread((threadroutine_t)NetReadThreadEx,
            pArg, g_pDefaultScheduleClass, g_uDefaultSchedulePolicy);
    if (NULL == pThread) {
        kprintf("*ERROR* failed to create kernelthread\n");
    }
    pThreadExitArg->pThread = pThread;

#if LWIP_DHCP
    if (0 == ip) {
        DHCPThreadExitArg *pDHCPThreadExitArg = PDHCPThreadExitArg(malloc(sizeof(DHCPThreadExitArg)));
        if (NULL == pDHCPThreadExitArg) {
            kprintf("*ERROR* malloc dhcp thread exit arg failed!\n");
            return;
        }
        pDHCPThreadExitArg->isExit = 0;
        set_dhcp_thread_exit_arg(pArg->pNetif, pDHCPThreadExitArg);

        Thread *pDHCPThread = CreateSystemThread((threadroutine_t)DHCP_Client,
            pArg->pNetif, g_pDefaultScheduleClass, g_uDefaultSchedulePolicy);
        pDHCPThreadExitArg->pThread = pDHCPThread;
        if (NULL == pDHCPThread) {
            kprintf("*ERROR* failed to create dhcp client thread!!\n");
            return;
        }
    }
#endif
}

extern int eth_deinit(wchar_t *ethernetName)
{
    if (NULL == ethernetName)
        return -1;
    netif_del(ethernetName);
    return 0;
}

extern "C" void net_thread_kill(void *pTArg)
{
    ECode ret = NOERROR;
    ((PNetThreadExitArg)pTArg)->isExit = 1;
    if (NULL != ((PNetThreadExitArg)pTArg)->pThread) {
        ret = (((PNetThreadExitArg)pTArg)->pThread)->Interrupt();
    }
    
    if (NOERROR == ret) {
        kprintf("Interrupt Thread Success!\n");
    }
    else if (E_THREAD_ALREADY_FINISHED == ret) {
        kprintf("Interrupt Thread has end!!\n");
    }
    else {
        kprintf("Interruput Thread error!\n");
    }
    //DzSleep(20, NULL);
}

extern "C" void rtl8139_output(unsigned char *p, int len, void *pNetDriver)
{
    ECode hr;
    MemoryBuf ebbOutData(p, len);
    hr = ((IDeviceDriver *)pNetDriver)->Write(0, ebbOutData, NULL, NULL);
    if (FAILED(hr)) {
        kprintf("*ERROR* failed to write driver pDriver=%08x\n", pNetDriver);
        return;
    }
}

extern "C" int get_thread_exit_flag(void *pThreadExitArg)
{
    assert(NULL != pThreadExitArg);
    return ((PDHCPThreadExitArg)pThreadExitArg)->isExit;
}

extern "C" void set_thread_exit_flag(void *pThreadExitArg, int flag)
{
    assert(NULL != pThreadExitArg);
    ((PDHCPThreadExitArg)pThreadExitArg)->isExit = flag;
}

STATIC uint_t s_uFieldIndex = 0;

extern "C" void* getthreadpointer()
{
    return (void*)DzGetThreadFieldAddress(s_uFieldIndex);
}

extern "C" void setthreadpointer(void* pAddress, void* pValue)
{
    #ifdef _win32
    DzSetSlotValue(s_uFieldIndex, pValue);
    #endif
}

#ifdef _win32
enum BreakPointType
{
    BreakPointType_Exec         = 0x01,
    BreakPointType_Write        = 0x02,
    BreakPointType_ReadWrite    = 0x03,
};
#endif
extern ECode SetGlobalBreakPoint(
        uint_t uNo, BreakPointType type, void *pvAddress, uint_t uLength);

extern "C" void setgloblepoint(void * addr)
{
    #ifdef KCONFIG_GLOBAL_BREAKPOINT
    SetGlobalBreakPoint(0, BreakPointType_Write, addr, 4);
    #endif
}

//////////////////////////////////////////////////////////////
void interface_init()
{
    /* loop_init done first */
    loop_init();
}

extern void lwipentry()
{
	static Boolean bInitial = FALSE;
	kprintf("lwipentry    !\n");
	if(!bInitial) {
	    assert(DzAcquireThreadFieldSlot(&s_uFieldIndex));
        bInitial = TRUE;
        netif_init();
	    mem_init();
	    memp_init();
	    pbuf_init();
	    stats_init();
	    raw_init();
	    tcpip_init(NULL, NULL);
	    interface_init();
	    RegisterInterface();
	}
}

//////////////////////sys_mbox//////////////////////////////
struct msgq{
    void *msg;
    struct msgq * next;
};
struct mbox{
    struct msgq * queue;
    struct msgq * last;
    DzMutex *mt;
    DzCondition *cond;
};
sys_mbox_t sys_mbox_new(void)
{
    struct mbox * queue = (struct mbox *)malloc(sizeof(struct mbox));
    queue->queue = NULL;
    queue->last = NULL;
    queue->mt = new DzMutex();
    queue->cond = new DzCondition();
    return (void*)queue;
}

void sys_mbox_post(sys_mbox_t mbox, void *msg)
{
    struct msgq * node = (struct msgq *)malloc(sizeof(struct msgq));
//    assert(msg);
    node->msg = msg;
    node->next = NULL;

    WaitResult wr;
    ((struct mbox *)mbox)->mt->Lock(&wr);
    struct msgq* cur = ((struct mbox *)mbox)->queue;
    if(cur){//boundary condition
        while(cur->next)cur = cur->next;//find tail
        cur->next = node;
    }
    else{
        ((struct mbox *)mbox)->queue = node;//cur is an other mbox->queue
    }
    assert(((struct mbox *)mbox)->queue);
    ((struct mbox *)mbox)->cond->Pulse();
    ((struct mbox *)mbox)->mt->Unlock();
}

u32_t sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)
{
    uint_t tm = 0;
    WaitResult wr;
    ((struct mbox *)mbox)->mt->Lock(&wr);
    while(((struct mbox *)mbox)->queue == NULL){
        tm = DzGetElapsedTicks();
        if(timeout){
            ((struct mbox *)mbox)->cond->TryWait(
                ((struct mbox*)mbox)->mt, DzMillisecondsToTicks(timeout), &wr);
        }
        else{
            ((struct mbox *)mbox)->cond->Wait(
                ((struct mbox*)mbox)->mt, &wr);
        }
        tm = DzGetElapsedTicks() - tm;
        if(wr == WaitResult_TimedOut){
            ((struct mbox *)mbox)->mt->Unlock();
            return 0xffffffff;
        }
    }
    assert(((struct mbox *)mbox)->queue);
    struct msgq * node = ((struct mbox *)mbox)->queue;
    ((struct mbox *)mbox)->queue = ((struct mbox *)mbox)->queue->next;
    ((struct mbox *)mbox)->mt->Unlock();
    if(msg)*msg = node->msg;//drop message
//    kprintf("*msg%08x_%08x\n", *msg, node->msg);
//    assert(*msg);
    free(node);
    return DzTicksToMilliseconds(tm);
}

void sys_mbox_free(sys_mbox_t mbox)
{
    struct msgq* node;
    while(((struct mbox *)mbox)->queue){
        node = ((struct mbox *)mbox)->queue;
        ((struct mbox *)mbox)->queue = ((struct mbox *)mbox)->queue->next;
        free(node);
    }
    delete ((struct mbox *)mbox)->cond;
    delete ((struct mbox *)mbox)->mt;
    free(mbox);
}


sys_thread_t sys_thread_new(void (* hthread)(void *arg), void *arg, int prio)
{
    Thread *pThread = CreateSystemThread((threadroutine_t)hthread,
            arg, g_pDefaultScheduleClass, g_uDefaultSchedulePolicy);
    if (!pThread) {
        kprintf("*ERROR* failed to create kernelthread\n");
    }
    return (sys_thread_t)pThread;
}

typedef struct sys_sem{
    s16_t count;
    s16_t ref;
    DzMutex *mt;
    DzCondition *cond;
}sys_sem;
sys_sem_t sys_sem_new(u8_t count)
{
   sys_sem* sem = (sys_sem*)malloc(sizeof(sys_sem));
    sem->count = count;
    sem->mt = new DzMutex();
    sem->cond = new DzCondition();
    sem->ref = 1;
    return sem;
}

void sys_sem_free(sys_sem_t sem)
{
//    kprintf(" del_%08x********************************************* \n\n", sem);
    delete ((sys_sem*)sem)->mt;
    delete ((sys_sem*)sem)->cond;
    free(sem);
}

void sys_sem_signal(sys_sem_t sem)
{
//    kprintf(" sin_%08x ", sem);

    assert(sem);
    WaitResult wr;
    if(((sys_sem*)sem)->ref == 1){
        ((sys_sem*)sem)->mt->Lock(&wr);
        ((sys_sem*)sem)->cond->Pulse();
//      kprintf(" s_%d ", ((sys_sem*)sem)->count);
        assert(((sys_sem*)sem)->count >= 0);
        (((sys_sem*)sem)->count)++;
        ((sys_sem*)sem)->mt->Unlock();
    }
//    else{
//        ((sys_sem*)sem)->mt->Lock();
//        ((sys_sem*)sem)->count = 0;
//        ((sys_sem*)sem)->mt->Unlock();
//    }

}

u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
{
    uint_t tm = 0;
    WaitResult wr;
//    kprintf(" wait_%08x ", sem);

    ((sys_sem*)sem)->mt->Lock(&wr);
    assert(((sys_sem*)sem)->count >= 0);
    while(((sys_sem*)sem)->count == 0){
        tm = DzGetElapsedTicks();
        if(timeout){
            ((sys_sem*)sem)->cond->TryWait(
                ((sys_sem*)sem)->mt, DzMillisecondsToTicks(timeout), &wr);
        }
        else{
            ((sys_sem*)sem)->cond->Wait(
                ((sys_sem*)sem)->mt, &wr);
        }

        tm = DzGetElapsedTicks() - tm;
//        kprintf("lost time %d\n", tm);
        if (wr == WaitResult_TimedOut) {
            ((sys_sem*)sem)->mt->Unlock();
            return 0xffffffff;
        }
    }
    assert(((sys_sem*)sem)->count > 0);
    (((sys_sem*)sem)->count)--;
    ((sys_sem*)sem)->mt->Unlock();
    return DzTicksToMilliseconds(tm);
}

static DzMutex s_mt;
sys_prot_t sys_arch_protect(void)
{
    WaitResult wr;
    s_mt.Lock(&wr);
    return (sys_prot_t)&s_mt;
}

void sys_arch_unprotect(sys_prot_t pval)
{
    ((DzMutex*)pval)->Unlock();
}

void* mt_malloc()
{
    return new DzMutex;
}

void mt_lock(void * mt)
{
    WaitResult wr;
    ((DzMutex*)mt)->Lock(&wr);
}

void mt_unlock(void* mt)
{
    ((DzMutex*)mt)->Unlock();
}

void sem_addref(void * mt, void* sem)
{
    WaitResult wr;
    ((DzMutex*)mt)->Lock(&wr);
    ((struct sys_sem*)sem)->ref = 1;
    ((DzMutex*)mt)->Unlock();
}

void sem_release(void * mt, void* sem)
{
    WaitResult wr;
    ((DzMutex*)mt)->Lock(&wr);
    ((struct sys_sem*)sem)->ref = 0;
    ((DzMutex*)mt)->Unlock();
}

void sem_releaseAndFree(void * sem)
{
    ((struct sys_sem*)sem)->ref = 0;
    delete ((sys_sem*)sem)->mt;
    delete ((sys_sem*)sem)->cond;
    free(sem);
}

void mt_free(void* mt)
{
    delete (DzMutex*)mt;
}

int triggerevent(int event)
{
//    kprintf("triggerevent\n");
    ((IEvent*)event)->Notify(SIGNALED);
    return 0;
}
